package org.dizena.common.trace.httpclient.handler;

import org.dizena.common.base.enums.EnumHeaderKey;
import org.dizena.common.base.enums.EnumLogLevel;
import org.dizena.common.logger.bean.LogInfo;
import org.dizena.common.logger.bean.LogRpcEvent;
import org.dizena.common.trace.httpclient.HttpClientTraceAutoProperties;
import org.dizena.common.utils.ReqUtil;
import org.dizena.common.utils.RpcUtil;
import org.slf4j.MDC;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.http.client.*;
import org.springframework.util.StreamUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;

public class HttpClientRequestTraceInterceptor implements ClientHttpRequestInterceptor {

    private String ipRegex = "(((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?))";

    private String domainRegex = ".*\\.(com|cn|com.cn|xyz|net|top|tech|org|gov|edu|pub|cn|biz|cc|tv|info|im)";

    private String additionalRegex = "(([0-9a-fA-F]{1,4}:){7}([0-9a-fA-F]){1,4}|([0-9a-fA-F]{1,4}:){1}(:[0-9a-fA-F]{1,4}){1,7})";

    private HttpClientTraceAutoProperties properties;

    private ClientHttpRequestFactory factory;
    private ApplicationEventPublisher publisher;

    public HttpClientRequestTraceInterceptor(HttpClientTraceAutoProperties properties, ClientHttpRequestFactory factory, ApplicationEventPublisher publisher) {
        this.properties = properties;
        this.factory = factory;
        this.publisher = publisher;
    }

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        final URI uri = request.getURI();
        String host = uri.getHost();
        ClientHttpResponse response;
        MDC.put(EnumHeaderKey.TRACE_ID.getKey(), ReqUtil.getTrace());
        Map<String, String> addHeader = ReqUtil.filterAllHeaderMap(properties.getKeys());
        addHeader.forEach((k, v) -> request.getHeaders().set(k, v));
        //log.info("rest {} header {}", request.getMethod().name(), request.getHeaders());
        if (host.matches(String.format("^%s|%s|%s$", ipRegex, domainRegex, additionalRegex))) {
            response = ipRequest(request, body);
        } else {
            response = execution.execute(request, body);
        }

        try (InputStream is = response.getBody()) {
            byte[] repBody = StreamUtils.copyToByteArray(is);
            String resultStr = new String(repBody);
            //log.info("rest {} response {},data={}", request.getMethod().name(), response.getStatusCode(), resultStr);
            resultStr = RpcUtil.wrapperResult(resultStr);
            //log.info("rest wrap {} response {},data={}", request.getMethod().name(), response.getStatusCode(), resultStr);
            byte[] modifiedBody = resultStr.getBytes();

            //logger
            LogInfo logInfo = new LogInfo();
            logInfo.setTraceId(ReqUtil.getTrace());
            logInfo.setClientIp(ReqUtil.getAllHeaderMap().get(EnumHeaderKey.REQUEST_IP));
            logInfo.setRequestTime(System.currentTimeMillis());
            logInfo.setRequestUrl(request.getURI().toString());
            logInfo.setRequestMethod(request.getMethod().name());
            logInfo.setRequestHeader(new HashMap<>(request.getHeaders()));
            logInfo.setRequestParam(new String(body));
            logInfo.setResponseBody(resultStr);
            publisher.publishEvent(new LogRpcEvent(this, logInfo, EnumLogLevel.INFO));

            return new CustomClientHttpResponse(response, modifiedBody);
        } catch (Exception e) {
            return response;
        }
    }

    private ClientHttpResponse ipRequest(HttpRequest httpRequest, byte[] body) throws IOException {
        ClientHttpRequest request = factory.createRequest(httpRequest.getURI(), httpRequest.getMethod());
        httpRequest.getHeaders().forEach((k, v) -> request.getHeaders().addAll(k, v));
        if (body != null && body.length > 0) {
            if (request instanceof StreamingHttpOutputMessage) {
                StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) request;
                streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
            } else {
                StreamUtils.copy(body, request.getBody());
            }
        }
        return request.execute();
    }

    class CustomClientHttpResponse implements ClientHttpResponse {
        private final ClientHttpResponse delegate;
        private final byte[] modifiedBody;

        public CustomClientHttpResponse(ClientHttpResponse delegate, byte[] modifiedBody) {
            this.delegate = delegate;
            this.modifiedBody = modifiedBody;
        }

        @Override
        public InputStream getBody() {
            return new ByteArrayInputStream(modifiedBody);
        }

        // 其他方法的实现可以直接委托给原始的 ClientHttpResponse
        @Override
        public HttpHeaders getHeaders() {
            return delegate.getHeaders();
        }

        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return delegate.getStatusCode();
        }

        @Override
        public int getRawStatusCode() throws IOException {
            return delegate.getRawStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return delegate.getStatusText();
        }

        @Override
        public void close() {
            delegate.close();
        }
    }

}
